using FileTransfer.Client.Http.Api;
using FileTransfer.Client.Http.Model;
using FileTransfer.Client.Upload;
using Microsoft.Extensions.Logging;

namespace FileTransfer.Client;

public class ShareCollection(ApiClient api, int chunkSize, ILoggerFactory? loggerFactory = null)
{
    private readonly FileUpload _fileUpload = new(api, chunkSize, loggerFactory);
    private ShareItemControllerApi ShareItemApi => api.ShareItemControllerApi;
    private readonly ILogger<ShareCollection>? _logger =
        loggerFactory?.CreateLogger<ShareCollection>();
    private readonly List<string> _files = new();

    public IReadOnlyList<string> Files => _files.AsReadOnly();

    public async Task AddFileAsync(
        string fileName,
        Func<Stream> getFile,
        FileUpload.ChunkProgressHandler? onProgress = null
    )
    {
        var fileMd5 = await _fileUpload.UploadFileAsync(
            fileName,
            getFile,
            (progressInfo) =>
            {
                _logger?.LogInformation("上传进度：{Progress}", progressInfo);
                onProgress?.Invoke(progressInfo);
            }
        );

        _files.Add(fileMd5);
    }

    public async Task AddFileAsync(
        FileInfo file,
        FileUpload.ChunkProgressHandler? onProgress = null
    )
    {
        var fileMd5 = await _fileUpload.UploadFileAsync(
            file,
            (progressInfo) =>
            {
                _logger?.LogInformation("上传进度：{Progress}", progressInfo);
                onProgress?.Invoke(progressInfo);
            }
        );

        _files.Add(fileMd5);
    }

    public record ShareResult(ShareItem Share, List<Uri> Links);

    public record ShareResultToken(ShareItem Share, string EditToken, List<Uri> Links);

    /// <summary>
    /// Creates a share with the uploaded files
    /// </summary>
    public async Task<ShareResultToken> MakeShareAsync()
    {
        if (_files.Count == 0)
        {
            throw new InvalidOperationException("Please upload files first");
        }

        // Create a share with distinct files
        var fileArray = _files.Distinct().ToList();
        _files.Clear();

        try
        {
            var newShareItemInfo = new NewShareItemInfo { Files = fileArray };
            var share = await ShareItemApi.CreateShareItemAsync(newShareItemInfo);
            var info = await ShareItemApi.GetShareItemByIdAsync(share.ShareItem.Code);

            var links = new List<Uri>();
            foreach (var file in info.Files)
            {
                var requestUri = ShareItemApi.GetShareItemFilesCall(
                    share.ShareItem.Code,
                    file.Md5,
                    file.FileName
                );

                links.Add(requestUri);
            }

            return new ShareResultToken(share.ShareItem, share.EditToken, links);
        }
        catch (Exception e)
        {
            throw new InvalidOperationException("Failed to create share", e);
        }
    }

    /// <summary>
    /// Gets share information by share code
    /// </summary>
    public static async Task<ShareResult> GetInfoByShareCodeAsync(ApiClient api, string shareCode)
    {
        var collection = new ShareCollection(api, 1024);
        return await collection.GetByShareCodeAsync(shareCode);
    }

    /// <summary>
    /// Gets share information by share code
    /// </summary>
    public async Task<ShareResult> GetByShareCodeAsync(string shareCode)
    {
        ShareItem info;
        try
        {
            info = await ShareItemApi.GetShareItemByIdAsync(shareCode);
        }
        catch (Exception e)
        {
            throw new InvalidOperationException(
                $"Failed to get share info for code: {shareCode}",
                e
            );
        }

        var links = new List<Uri>();
        foreach (var file in info.Files)
        {
            try
            {
                var requestUri = ShareItemApi.GetShareItemFilesCall(
                    shareCode,
                    file.Md5,
                    file.FileName
                );
                links.Add(requestUri);
            }
            catch (Exception e)
            {
                throw new InvalidOperationException(
                    $"Failed to get download link for file: {file.FileName}",
                    e
                );
            }
        }

        return new ShareResult(info, links);
    }

    /// <summary>
    /// Gets the download link for a file in a share
    /// </summary>
    public static Uri GetDownloadLink(ApiClient api, string code, Http.Model.File file)
    {
        try
        {
            var uri = api.ShareItemControllerApi.GetShareItemFilesCall(
                code,
                file.Md5,
                file.FileName
            );
            return uri;
        }
        catch (Exception e)
        {
            throw new InvalidOperationException(
                $"Failed to get download link for file: {file.FileName}",
                e
            );
        }
    }
}
